package com.ironman.analyze.core.service;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.util.StrUtil;
import com.ironman.analyze.common.dataobject.TrainingData;
import com.ironman.analyze.common.dataobject.Wind;
import com.ironman.analyze.core.compute.*;
import com.ironman.analyze.core.compute.entity.Training;
import com.ironman.analyze.core.compute.entity.WindEnvWindowData;
import com.ironman.analyze.core.compute.factory.OriginalDataFactory;
import com.ironman.analyze.core.compute.factory.OriginalDataWrapper;
import com.ironman.analyze.core.context.TrainingContext;
import com.ironman.analyze.orm.flex.TrainingDataMapper;
import com.ironman.common.SailingParamWrapper;
import com.ironman.common.entity.WindData;
import com.ironman.common.params.*;
import com.ironman.common.threadpool.ThreadPool;
import com.ironman.common.util.AccuracyUtil;
import com.ironman.common.util.UnitConvertUtil;
import com.mybatisflex.core.datasource.DataSourceKey;
import com.mybatisflex.core.row.Db;
import com.mybatisflex.core.row.Row;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class TrainingService {

    @Resource
    TrainingDataMapper trainingDataMapper;

    public List<Tuple> standardParams(String tenantCode, String levelId) {
        List<Row> rowList = DataSourceKey.use("db", () -> Db.selectListBySql("select * from standard_params where tenant_code = ? and level_id = ?", tenantCode, levelId));

        return rowList.stream().map(n -> {
            double member0;
            double member1;
            double member2;
            double member3;

            String windSpeedUnit = n.getString("wind_speed_unit");
            Double windSpeedBegin = n.getDouble("wind_speed_begin");
            if (Objects.isNull(windSpeedBegin)) {
                member0 = Double.MIN_VALUE;
            } else {
                member0 = UnitConvertUtil.unitConvert(windSpeedBegin, windSpeedUnit);
            }
            Double windSpeedEnd = n.getDouble("wind_speed_end");
            if (Objects.isNull(windSpeedEnd)) {
                member1 = Double.MAX_VALUE;
            } else {
                member1 = UnitConvertUtil.unitConvert(windSpeedEnd, windSpeedUnit);
            }

            Double shipSpeed = n.getDouble("ship_speed");
            String shipSpeedUnit = n.getString("ship_speed_unit");
            member2 = UnitConvertUtil.unitConvert(shipSpeed, shipSpeedUnit);

            Double vmg = n.getDouble("vmg");
            String vmgUnit = n.getString("vmg_unit");
            member3 = UnitConvertUtil.unitConvert(vmg, vmgUnit);

            String type = n.getString("type");
            return new Tuple(type, member0, member1, member2, member3);
        }).collect(Collectors.toList());
    }

    public String mHmName(String mHmId) {
        Row row = DataSourceKey.use("db", () -> Db.selectOneBySql("select user_name from person where person_id = ? and status = '01'", mHmId));
        return Optional.ofNullable(row).map(n -> n.getString("user_name")).orElse("--");
    }

    public List<String> sportsman(String mHmId) {
        Row row = DataSourceKey.use("db", () -> Db.selectOneBySql("select group_id from person_sailboat_group where person_id = ? and first_person = '01'", mHmId));
        String groupId = Optional.ofNullable(row)
                .map(n -> n.getString("group_id")).orElse("");
        if (StrUtil.isNotBlank(groupId)) {
            List<Row> rowList = DataSourceKey.use("db", () -> Db.selectListBySql("select person_id from person_sailboat_group where group_id = ? and type = '01' order by sort", groupId));
            List<String> personIdList = rowList.stream().map(n -> n.getString("person_id")).collect(Collectors.toList());
            if (!personIdList.isEmpty()) {
                String sql = StrUtil.format("select user_name from person where person_id in ({})", personIdList.stream().map(n -> StrUtil.format("'{}'", n)).collect(Collectors.joining(",")));
                List<Row> personList = DataSourceKey.use("db", () -> Db.selectListBySql(sql));
                return personList.stream().map(n -> n.getString("user_name")).collect(Collectors.toList());
            }
        }
        return new ArrayList<>();
    }

    public List<String> wind(String tenantCode, Date startTime, Date endTime, WindParam windParam) {
        StopWatch sw = new StopWatch();
        sw.start("查询风环境数据");
        List<Wind> windList = trainingDataMapper.selectWind(tenantCode, startTime, endTime);
        sw.stop();

        sw.start("教练艇分组计算");
        Map<Date, List<WindEnvWindowData>> windAllWindowDataMapList = new HashMap<>();
        Map<String, List<Wind>> groupWindList = windList.stream().collect(Collectors.groupingBy(Wind::getTrainingBoatCode));
        List<CompletableFuture<StopWatch>> futureList = new ArrayList<>();
        for (Map.Entry<String, List<Wind>> entry : groupWindList.entrySet()) {
            CompletableFuture<StopWatch> windVectorComputer = CompletableFuture.supplyAsync(() -> {
                StopWatch windSw = new StopWatch("开窗计算>" + entry.getKey() + ">Wind");
                windSw.start();
                AllWindVectorComputer computer = new AllWindVectorComputer(windParam, windAllWindowDataMapList);
                List<WindData> dataList = entry.getValue().stream().map(n -> new WindData(n.getTs(), entry.getKey(), n.getWindSpeed(), n.getWindDirection())).collect(Collectors.toList());
                computer.computeWindowData(dataList);
                return windSw;
            }, ThreadPool.SAILBOAT_COMPUTE_EXECUTOR).whenComplete((stopWatch, throwable) -> {
                stopWatch.stop();
                log.info(stopWatch.prettyPrint(TimeUnit.MILLISECONDS));
            });
            futureList.add(windVectorComputer);
        }
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).exceptionally(throwable -> {
            log.error("风数据计算异常", throwable);
            return null;
        });
        allFutures.join();
        sw.stop();

        sw.start("时间戳排序");
        List<Date> tsList = new ArrayList<>(windAllWindowDataMapList.keySet());
        Collections.sort(tsList);
        sw.stop();

        sw.start("构建响应报文");
        List<String> resList = tsList.stream().map(ts -> {
            List<WindEnvWindowData> windEnvWindowDataList = windAllWindowDataMapList.get(ts);
            String allWind = Optional.ofNullable(windEnvWindowDataList).map(m -> m.stream().map(n -> StrUtil.format("{}|{}|{}",
                            n.getTrainingBoatCode(),
                            AccuracyUtil.halfUpSpeedDouble(n.getWindSpeed()),
                            AccuracyUtil.downDirectionInteger(n.getWindDirection()))).collect(Collectors.joining("#")))
                    .orElse("");
            return StrUtil.format("{},{}", ts.getTime() / 1000, allWind);
        }).collect(Collectors.toList());
        sw.stop();

        log.info(sw.prettyPrint(TimeUnit.MILLISECONDS));
        return resList;
    }

    public Training build(String tenantCode, String levelId, String mHmId, Date trainingDay, Date startTime, Date endTime, boolean report, SailingParamWrapper sailingParamWrapper) {
        HeadingParam headingParam = sailingParamWrapper.getHeadingParam();
        HeartrateParam heartrateParam = sailingParamWrapper.getHeartrateParam();
        RolParam rolParam = sailingParamWrapper.getRolParam();
        SpeedParam speedParam = sailingParamWrapper.getSpeedParam();
        WindParam windParam = sailingParamWrapper.getWindParam();
        YawParam yawParam = sailingParamWrapper.getYawParam();

        String mHmName = mHmName(mHmId);
        List<Tuple> standardParams = standardParams(tenantCode, levelId);

        CompletableFuture<Training> trainingTrackCompletableFuture = CompletableFuture.supplyAsync(() -> {
            String swId = StrUtil.format("{} => {} [{} - {}]", report ? "报告计算" : "轨迹计算", mHmId, DateUtil.format(startTime, DatePattern.NORM_DATETIME_PATTERN), DateUtil.format(endTime, DatePattern.NORM_DATETIME_PATTERN));
            StopWatch sw = new StopWatch(swId);
            sw.start("查询主舵手运动数据");
            List<TrainingData> trainingDataList = trainingDataMapper.selectSubTable(tenantCode + "_" + DateUtil.format(trainingDay, DatePattern.PURE_DATE_FORMAT) + "_" + mHmId, startTime, endTime);
            sw.stop();

            sw.start("原数据预处理");
            OriginalDataWrapper originalDataWrapper = OriginalDataFactory.build(
                    trainingDataList,
                    windParam.getWindowSize(),
                    headingParam.getWindowSize(),
                    speedParam.getWindowSize(),
                    heartrateParam.getWindowSize(),
                    rolParam.getWindowSize(),
                    yawParam.getWindowSize());
            sw.stop();

            sw.start("开窗计算");

            TrainingContext trainingContext = new TrainingContext(standardParams, rolParam);

            CompletableFuture<StopWatch> futureGpsEmptyComputer = CompletableFuture.supplyAsync(() -> {
                StopWatch gpsSw = new StopWatch("开窗计算>" + mHmId + ">Gps");
                gpsSw.start();
                GpsEmptyComputer computer = new GpsEmptyComputer(trainingContext);
                computer.computeWindowData(originalDataWrapper.getGpsDataList());
                return gpsSw;
            }, ThreadPool.SAILBOAT_COMPUTE_EXECUTOR).whenComplete((res, e) -> {
                res.stop();
                log.info(res.prettyPrint(TimeUnit.MILLISECONDS));
            });

            CompletableFuture<StopWatch> futureHeadingVectorComputer = CompletableFuture.supplyAsync(() -> {
                StopWatch headingSw = new StopWatch("开窗计算>" + mHmId + ">Heading");
                headingSw.start();
                HeadingVectorComputer computer = new HeadingVectorComputer(trainingContext, headingParam);
                computer.computeWindowData(originalDataWrapper.getHeadingDataList());
                return headingSw;
            }, ThreadPool.SAILBOAT_COMPUTE_EXECUTOR).whenComplete((res, e) -> {
                res.stop();
                log.info(res.prettyPrint(TimeUnit.MILLISECONDS));
            });

            CompletableFuture<StopWatch> futureHeartrateAverageComputer1 = CompletableFuture.supplyAsync(() -> {
                StopWatch heartrateSw = new StopWatch("开窗计算>" + mHmId + ">Heartrate1");
                heartrateSw.start();
                Heartrate1AverageComputer computer = new Heartrate1AverageComputer(trainingContext, heartrateParam);
                computer.computeWindowData(originalDataWrapper.getHeartrateDataList1());
                return heartrateSw;
            }, ThreadPool.SAILBOAT_COMPUTE_EXECUTOR).whenComplete((res, e) -> {
                res.stop();
                log.info(res.prettyPrint(TimeUnit.MILLISECONDS));
            });

            CompletableFuture<StopWatch> futureHeartrateAverageComputer2 = CompletableFuture.supplyAsync(() -> {
                StopWatch heartrateSw = new StopWatch("开窗计算>" + mHmId + ">Heartrate2");
                heartrateSw.start();
                Heartrate2AverageComputer computer = new Heartrate2AverageComputer(trainingContext, heartrateParam);
                computer.computeWindowData(originalDataWrapper.getHeartrateDataList2());
                return heartrateSw;
            }, ThreadPool.SAILBOAT_COMPUTE_EXECUTOR).whenComplete((res, e) -> {
                res.stop();
                log.info(res.prettyPrint(TimeUnit.MILLISECONDS));
            });

            CompletableFuture<StopWatch> futureRolAverageComputer = CompletableFuture.supplyAsync(() -> {
                StopWatch rolSw = new StopWatch("开窗计算>" + mHmId + ">Rol");
                rolSw.start();
                RolAverageComputer computer = new RolAverageComputer(trainingContext, rolParam);
                computer.computeWindowData(originalDataWrapper.getRolDataList());
                return rolSw;
            }, ThreadPool.SAILBOAT_COMPUTE_EXECUTOR).whenComplete((res, e) -> {
                res.stop();
                log.info(res.prettyPrint(TimeUnit.MILLISECONDS));
            });

            CompletableFuture<StopWatch> futureSpeedAverageComputer = CompletableFuture.supplyAsync(() -> {
                StopWatch speedSw = new StopWatch("开窗计算>" + mHmId + ">Speed");
                speedSw.start();
                SpeedAverageComputer computer = new SpeedAverageComputer(trainingContext, speedParam);
                computer.computeWindowData(originalDataWrapper.getSpeedDataList());
                return speedSw;
            }, ThreadPool.SAILBOAT_COMPUTE_EXECUTOR).whenComplete((res, e) -> {
                res.stop();
                log.info(res.prettyPrint(TimeUnit.MILLISECONDS));
            });

            CompletableFuture<StopWatch> futureAllWindVectorComputer = CompletableFuture.supplyAsync(() -> {
                StopWatch windSw = new StopWatch("开窗计算>" + mHmId + ">All Wind");
                windSw.start();
                List<Wind> windList = trainingDataMapper.selectWind(tenantCode, startTime, endTime);
                Map<String, List<Wind>> groupWindList = windList.stream().collect(Collectors.groupingBy(Wind::getTrainingBoatCode));
                for (Map.Entry<String, List<Wind>> entry : groupWindList.entrySet()) {
                    WindVectorComputer computer = new WindVectorComputer(trainingContext, windParam);
                    List<WindData> dataList = entry.getValue().stream().map(n -> new WindData(n.getTs(), entry.getKey(), n.getWindSpeed(), n.getWindDirection())).collect(Collectors.toList());
                    computer.computeWindowData(dataList);
                }
                return windSw;
            }, ThreadPool.SAILBOAT_COMPUTE_EXECUTOR).whenComplete((res, e) -> {
                res.stop();
                log.info(res.prettyPrint(TimeUnit.MILLISECONDS));
            });

            CompletableFuture<StopWatch> futureYawVectorComputer = CompletableFuture.supplyAsync(() -> {
                StopWatch yawSw = new StopWatch("开窗计算>" + mHmId + ">Yaw");
                yawSw.start();
                YawVectorComputer computer = new YawVectorComputer(trainingContext, yawParam);
                computer.computeWindowData(originalDataWrapper.getYawDataList());
                return yawSw;
            }, ThreadPool.SAILBOAT_COMPUTE_EXECUTOR).whenComplete((res, e) -> {
                res.stop();
                log.info(res.prettyPrint(TimeUnit.MILLISECONDS));
            });

            CompletableFuture<Void> allFutures = CompletableFuture.allOf(
                    futureGpsEmptyComputer,
                    futureHeadingVectorComputer,
                    futureHeartrateAverageComputer1,
                    futureHeartrateAverageComputer2,
                    futureRolAverageComputer,
                    futureSpeedAverageComputer,
                    futureAllWindVectorComputer,
                    futureYawVectorComputer
            );
            allFutures.join();

            sw.stop();

            sw.start("构建Training");
            Training training;
            if (report) {
                training = trainingContext.report(originalDataWrapper.getTupleList(), mHmId, mHmName, sportsman(mHmId));
            } else {
                training = trainingContext.track(originalDataWrapper.getTupleList(), mHmId, mHmName);
            }
            sw.stop();

            log.info(sw.prettyPrint(TimeUnit.MILLISECONDS));
            return training;
        }, ThreadPool.SAILBOAT_COMPUTE_EXECUTOR).exceptionally(throwable -> {
            log.error("{} 轨迹计算发生异常", mHmId, throwable);
            return null;
        });
        return trainingTrackCompletableFuture.join();
    }
}
